/**
 * @(#)Client.java, Sep 7, 2012. 
 * 
 * Copyright 2012 Yodao, Inc. All rights reserved.
 * YODAO PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.buaaer.mina;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

/**
 *
 * @author wangheng
 *
 */
public class Client {
    
    /**
     * server address
     */
    private InetSocketAddress addr;
    
    /**
     * timeout of connect to server
     */
    private long connectTimeout = 1000;
    
    /**
     * socket write timeout
     */
    private long writeTimeout = 1000;
    
    /**
     * whether open the connection to server
     */
    private AtomicBoolean open = new AtomicBoolean(false);
    
    /**
     * client session 
     */
    private IoSession ioSession;
    
    /**
     * client request sequence id
     */
    private AtomicLong seqId = new AtomicLong(0);
    
    /**
     * the context map
     */
    private Map<Long, FutureResult> contextMap = new ConcurrentHashMap<Long, FutureResult>();
    
    /**
     * constructor 
     * 
     * @param addr
     */
    public Client(InetSocketAddress addr) {
        this.addr = addr;
    }
    
    /**
     * init the client
     * 
     * @return
     */
    public boolean init() {
        
        NioSocketConnector connector = new NioSocketConnector();
        connector.setConnectTimeoutMillis(this.connectTimeout);
        connector.getFilterChain().addLast("codec", 
                new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
        
        ClientHandler clientHandler = new ClientHandler(this);
        connector.setHandler(clientHandler);
    
        ConnectFuture connectFuture = connector.connect(addr);
        connectFuture.awaitUninterruptibly();
        ioSession = connectFuture.getSession();
        ioSession.getConfig().setWriteTimeout((int)(writeTimeout/1000));
        
        this.open.set(true);
        
        return true;
    }
    
    /**
     * submit the request to server and this operation is asynchronous; 
     * 
     * @param object
     * @return
     * @throws Exception
     */
    public FutureResult submit(CallBody object) throws Exception {
        
        if(!this.open.get()) {
            throw new Exception("connection is not open");
        }
        
        FutureResult result = new FutureResult();
        
        long seqId = this.seqId.incrementAndGet() ;
        DataPackage data = new DataPackage(seqId, object);
        
        this.contextMap.put(seqId, result);
        ioSession.write(data);
        return result;
            

    }
    
    /**
     * once close the client, we should set the open to false, 
     * close the ioSession and finish all the futureResult 
     */
    public void close() {
        this.open.set(false);
        this.ioSession.close(true);
        for(Map.Entry<Long, FutureResult> entry : this.contextMap.entrySet()) {
            Long seqId = entry.getKey();
            FutureResult result = entry.getValue();
            
            DataPackage exceptionPackage = new DataPackage(seqId, 
                    new Exception("client has been closed"));
            
            result.finish(exceptionPackage);
        }
    }
    
    
    public Map<Long, FutureResult> getContextMap() {
        return contextMap;
    }

}


